/**
 * @fileOverview 队列
 */



var Base = require('../base');
var Uploader = require('../uploader');
var Queue = require('../queue');
var WUFile = require('../file');
var File = require('../lib/file');
var RuntimeClient = require('../runtime/client');
require('./widget');
var $ = Base.$, rExt = /\.\w+$/, Status = WUFile.Status;
module.exports = Uploader.register({
    name: 'queue',
    init: function (opts) {
        var me = this, deferred, len, i, item, arr, accept, runtime;
        if ($.isPlainObject(opts.accept)) {
            opts.accept = [opts.accept];
        }
        // accept中的中生成匹配正则。
        if (opts.accept) {
            arr = [];
            for (i = 0, len = opts.accept.length; i < len; i++) {
                item = opts.accept[i].extensions;
                item && arr.push(item);
            }
            if (arr.length) {
                accept = '\\.' + arr.join(',').replace(/,/g, '$|\\.').replace(/\*/g, '.*') + '$';
            }
            me.accept = new RegExp(accept, 'i');
        }
        me.queue = new Queue();
        me.stats = me.queue.stats;
        // 如果当前不是html5运行时，那就算了。
        // 不执行后续操作
        if (this.request('predict-runtime-type') !== 'html5') {
            return;
        }
        // 创建一个 html5 运行时的 placeholder
        // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
        deferred = Base.Deferred();
        this.placeholder = runtime = new RuntimeClient('Placeholder');
        runtime.connectRuntime({ runtimeOrder: 'html5' }, function () {
            me._ruid = runtime.getRuid();
            deferred.resolve();
        });
        return deferred.promise();
    },
    // 为了支持外部直接添加一个原生File对象。
    _wrapFile: function (file) {
        if (!(file instanceof WUFile)) {
            if (!(file instanceof File)) {
                if (!this._ruid) {
                    throw new Error('Can\'t add external files.');
                }
                file = new File(this._ruid, file);
            }
            file = new WUFile(file);
        }
        return file;
    },
    // 判断文件是否可以被加入队列
    acceptFile: function (file) {
        var invalid = !file || !file.size || this.accept && // 如果名字中有后缀，才做后缀白名单处理。
        rExt.exec(file.name) && !this.accept.test(file.name);
        return !invalid;
    },
    /**
         * @event beforeFileQueued
         * @param {File} file File对象
         * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。
         * @for  Uploader
         */
    /**
         * @event fileQueued
         * @param {File} file File对象
         * @description 当文件被加入队列以后触发。
         * @for  Uploader
         */
    _addFile: function (file) {
        var me = this;
        file = me._wrapFile(file);
        // 不过类型判断允许不允许，先派送 `beforeFileQueued`
        if (!me.owner.trigger('beforeFileQueued', file)) {
            return;
        }
        // 类型不匹配，则派送错误事件，并返回。
        if (!me.acceptFile(file)) {
            me.owner.trigger('error', 'Q_TYPE_DENIED', file);
            return;
        }
        me.queue.append(file);
        me.owner.trigger('fileQueued', file);
        return file;
    },
    getFile: function (fileId) {
        return this.queue.getFile(fileId);
    },
    /**
         * @event filesQueued
         * @param {File} files 数组，内容为原始File(lib/File）对象。
         * @description 当一批文件添加进队列以后触发。
         * @for  Uploader
         */
    /**
         * @property {Boolean} [auto=false]
         * @namespace options
         * @for Uploader
         * @description 设置为 true 后，不需要手动调用上传，有文件选择即开始上传。
         * 
         */
    /**
         * @method addFiles
         * @grammar addFiles( file ) => undefined
         * @grammar addFiles( [file1, file2 ...] ) => undefined
         * @param {Array of File or File} [files] Files 对象 数组
         * @description 添加文件到队列
         * @for  Uploader
         */
    addFile: function (files) {
        var me = this;
        if (!files.length) {
            files = [files];
        }
        files = $.map(files, function (file) {
            return me._addFile(file);
        });
        if (files.length) {
            me.owner.trigger('filesQueued', files);
            if (me.options.auto) {
                setTimeout(function () {
                    me.request('start-upload');
                }, 20);
            }
        }
    },
    getStats: function () {
        return this.stats;
    },
    /**
         * @event fileDequeued
         * @param {File} file File对象
         * @description 当文件被移除队列后触发。
         * @for  Uploader
         */
    /**
         * @method removeFile
         * @grammar removeFile( file ) => undefined
         * @grammar removeFile( id ) => undefined
         * @grammar removeFile( file, true ) => undefined
         * @grammar removeFile( id, true ) => undefined
         * @param {File|id} file File对象或这File对象的id
         * @description 移除某一文件, 默认只会标记文件状态为已取消，如果第二个参数为 `true` 则会从 queue 中移除。
         * @for  Uploader
         * @example
         *
         * $li.on('click', '.remove-this', function() {
         *     uploader.removeFile( file );
         * })
         */
    removeFile: function (file, remove) {
        var me = this;
        file = file.id ? file : me.queue.getFile(file);
        this.request('cancel-file', file);
        if (remove) {
            this.queue.removeFile(file);
        }
    },
    /**
         * @method getFiles
         * @grammar getFiles() => Array
         * @grammar getFiles( status1, status2, status... ) => Array
         * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。
         * @for  Uploader
         * @example
         * console.log( uploader.getFiles() );    // => all files
         * console.log( uploader.getFiles('error') )    // => all error files.
         */
    getFiles: function () {
        return this.queue.getFiles.apply(this.queue, arguments);
    },
    fetchFile: function () {
        return this.queue.fetch.apply(this.queue, arguments);
    },
    /**
         * @method retry
         * @grammar retry() => undefined
         * @grammar retry( file ) => undefined
         * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。
         * @for  Uploader
         * @example
         * function retry() {
         *     uploader.retry();
         * }
         */
    retry: function (file, noForceStart) {
        var me = this, files, i, len;
        if (file) {
            file = file.id ? file : me.queue.getFile(file);
            file.setStatus(Status.QUEUED);
            noForceStart || me.request('start-upload');
            return;
        }
        files = me.queue.getFiles(Status.ERROR);
        i = 0;
        len = files.length;
        for (; i < len; i++) {
            file = files[i];
            file.setStatus(Status.QUEUED);
        }
        me.request('start-upload');
    },
    /**
         * @method sort
         * @grammar sort( fn ) => undefined
         * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。
         * @for  Uploader
         */
    sortFiles: function () {
        return this.queue.sort.apply(this.queue, arguments);
    },
    /**
         * @event reset
         * @description 当 uploader 被重置的时候触发。
         * @for  Uploader
         */
    /**
         * @method reset
         * @grammar reset() => undefined
         * @description 重置uploader。目前只重置了队列。
         * @for  Uploader
         * @example
         * uploader.reset();
         */
    reset: function () {
        this.owner.trigger('reset');
        this.queue = new Queue();
        this.stats = this.queue.stats;
    },
    destroy: function () {
        this.reset();
        this.placeholder && this.placeholder.destroy();
    }
}) || module.exports;;